package Implementation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import interfaces.ServerInterface;
import entility.Job;
import entility.JobImpl;
import entility.JobMap;
import entility.Machine;
import entility.Notification;
import entility.Range;

public class ServerImpl implements ServerInterface {

	public static List<Machine> listHosts;
	public static List<Machine> listServers;
	public static Map<String, Integer> hostsAndJobs;
	public static Machine me;
	private static boolean lazyHosts;
	public static HashMap<String,Boolean> amostragemConcluida = new HashMap<String,Boolean>();
	public static Map<Machine, List<Range> > intervalos = new HashMap<Machine, List<Range>>();
	
	
	public static ServerImpl instance;

	
	private ServerImpl(Machine server) {
		super();
		me = server;
		if (ServerImpl.instance == null) {
			listHosts = new ArrayList<Machine>();
			listServers = new ArrayList<Machine>();
			hostsAndJobs = new HashMap<String, Integer>();
		}
		this.addServer(me);
	}

	private ServerImpl(Machine server, Machine target) {
		super();
		me = server;

		if (ServerImpl.instance == null) {


			try {
				Job getHostsFromPrimaryServer = new JobImpl(Notification.SERVER);
				getHostsFromPrimaryServer.setMethod("getHosts");
				Job response = target.getMessage(getHostsFromPrimaryServer);
				listHosts =  response.getMachineList();


				Job getListOfJobsFromPrimaryServer = new JobImpl(Notification.SERVER);
				getListOfJobsFromPrimaryServer.setMethod("getHostsAndJobs");
				Job listOfJobs = target.getMessage(getListOfJobsFromPrimaryServer);
				hostsAndJobs = new JobMap(listOfJobs);



				listServers = new ArrayList<Machine>();

			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		this.addServer(target);
		this.addServer(me);
	}

	@Override
	public void addHost(Machine host) {
		boolean hasAdded = false;
		/* verify if the server had been added before */
		for (Machine hostBack : listHosts) {
			if (hostBack.ip.equals(host.ip))
				hasAdded = true;
		}
		if (!hasAdded) {
			Job job = new JobImpl(Notification.SERVER);
			job.setMethod("addHost");
			job.setData(host);
			listHosts.add(host);
			amostragemConcluida.put(host.ip, false);

			for (Machine serv : listServers) {
				if (!serv.ip.equals(me.ip) )
					try {
						serv.getMessage(job);
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
		}

	}

	
	
	@Override
	public void remove(Machine host) {


		boolean removed = true;
		for (Machine m:listHosts){
			if (m.ip.equals(host.ip)) //se n�o existir nenhum ip igual ao do host ele já foi deletado??????
				removed = false;	
		}


		if (!removed){ /*se n�o foi removido ainda, remove*/

			Job job = new JobImpl(Notification.SERVER);
			job.setMethod("removeHost");
			job.setData(host);
			listHosts.remove(getIndex(host));
			hostsAndJobs.remove(host.ip);

			for (Machine serv : listServers) {
				if (!serv.equals(me))
					try {
						serv.getMessage(job);
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}			
		}



	}

	private int getIndex(Machine host) {
		// TODO Auto-generated method stub

		for(int i=0; i<listHosts.size(); i++)
		{
			if(listHosts.get(i).ip.equals(host.ip))
				return i;

		}
		return -1;


	}

	/**/
	/* */	
	@Override
	public void addServer(Machine server) {
		// TODO Auto-generated method stub
		boolean hasAdded = false;
		/* verify if the server had been added before */
		for (Machine serv : listServers) {
			if (serv.ip.equals(server.ip))
				hasAdded = true;
		}
		if (!hasAdded) {
			Job job = new JobImpl(Notification.SERVER);
			job.setMethod("addServer");
			job.setData(me);
			listServers.add(server);

			if (!server.ip.equals(me.ip)) {

				for (Machine serv : listServers) {
					try {
						serv.getMessage(job);

					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			}
		}
	}

	public void imprime(){
		System.out.println("Conectou" + me.ip);

	}

	

	public static ServerInterface getInstance(Machine mac) {
		// TODO Auto-generated method stub

		if (ServerImpl.instance == null)
			instance = new ServerImpl(mac);

		return instance;
	}

	public static ServerInterface getInstance(Machine mac, Machine primaryServer) {
		// TODO Auto-generated method stub

		if (ServerImpl.instance == null)
			instance = new ServerImpl(mac, primaryServer);

		return instance;
	}

	@Override
	public String toString() {
		return "ServerImpl [listHosts=" + listHosts + ", listServers="
				+ listServers + ", me=" + me + "]";
	}

	@Override
	public void notify(Notification notification) {

		if(notification.getType().equals(Notification.HOST))
		{

			if(notification.getMessage().equals(Notification.MESSAGEERROR))
			{
				this.remove(notification.getMachine());
			}

		}


		if(notification.getType().equals(Notification.SERVER))
		{

			if(notification.getMessage().equals(Notification.MESSAGEERROR))
			{
				this.removeServer(notification.getMachine());
			}

		}



	}

	public void removeServer(Machine server) {

		boolean removed = true;
		for (Machine m:listServers){
			if (m.ip.equals(server.ip)) //se n�o existir nenhum ip igual ao do host ele j� foi deletado??????
				removed = false;	
		}


		if (!removed){ /*se n�o foi removido ainda, remove*/

			Job job = new JobImpl(Notification.SERVER);
			job.setMethod("removeServer");
			job.setData(server);
			listServers.remove(getIndexServer(server));

			if(!server.ip.equals(me.ip))
			{
				for (Machine serv : listServers) {
					if (!serv.ip.equals(server.ip))
						try {
							serv.getMessage(job);
						} catch (ClassNotFoundException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				}
			}

		}





	}

	private int getIndexServer(Machine server) {
		// TODO Auto-generated method stub

		List<Machine> serverList = new ArrayList<Machine>(listServers);
		for(int i=0; i<serverList.size(); i++)
		{
			if(serverList.get(i).ip.equals(server.ip))
				return i;

		}
		return -1;
	}

	@Override
	public void updateQuatityOfJobs(Job msg) {
		
		Machine host = new Machine(msg.getData().get(0));
		Integer numberOfJobs = Integer.parseInt(msg.getData().get(1));
		Integer lastNumberOfJobs = hostsAndJobs.get(host.ip);
		hostsAndJobs.put(host.ip, numberOfJobs);
		
		
		/** Sending the number of jobs to other Servers*/
		for(Machine m : listServers)
		{
			if(!m.ip.equals(me.ip) && lastNumberOfJobs != numberOfJobs && !msg.getHead().equals(Notification.SERVER) )
			{
				try {
					msg.Origin(Notification.SERVER);
					m.getMessage(msg);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}		
				
			}
		}
		this.checkForLazyHosts();
		
		/**Sharing the jobs to another hosts*/
		if(lastNumberOfJobs != numberOfJobs && lazyHosts )
		{ 
			Machine BusyHost = this.getTheHostMoreBusy();
			Machine LazyHost = this.getLazyHost();
			if(this.numberOfTotalJobs() > 0 && BusyHost != null && LazyHost != null)
			{
				Job shareYourJobs = new JobImpl(Notification.SERVER);
				shareYourJobs.setMethod("shareYourJobs");
				shareYourJobs.setData(LazyHost);
				
				/*try {
					BusyHost.getMessage(shareYourJobs);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					
					System.out.println("Socket esta sendo utilizado para este host");
					if(e.getMessage().trim().equals("Socket closed"))
					{
						try {
							Thread.sleep(700);
							BusyHost.getMessage(shareYourJobs);
							
						} catch (Exception e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
					}
				}*/
				
			}
		}

	}

	private Machine getLazyHost() {
		
		for(String hostIp: hostsAndJobs.keySet())
		{
			if(hostsAndJobs.get(hostIp) == 0)
				return getHostByIp(hostIp);
			
		}
		return null;
	
	}

	private void checkForLazyHosts() {

		for(Integer numberOfJobs: hostsAndJobs.values())
		{
			if(numberOfJobs == 0)
			{
				lazyHosts = true;
				return;
			}
			
		}
		lazyHosts = false;
		

	}


	
	private Integer numberOfTotalJobs()
	{
		Integer numberOfJobs = 0;
		for(Integer jobs: hostsAndJobs.values())
		{
			numberOfJobs = numberOfJobs + jobs;
		}
		return numberOfJobs;
	}
	
	private Machine getTheHostMoreBusy()
	{
		
		if(listHosts.size() < 2)
			return null;
		
		
		String hostIp = "";
		Integer maxJob = 0;
		
		
		for(String hosts: hostsAndJobs.keySet())
		{
			Integer nJob = hostsAndJobs.get(hosts);
			if( nJob > maxJob)
			{
				hostIp = hosts;
				maxJob = nJob;
			}
		}
		
		if(maxJob < 2 )
			return null;
		
		/**Caso retornar nulo aqui significa que hosts e hostsANdJobs estao diferentes*/
		
		return this.getHostByIp(hostIp);
		
	}
	
	private Machine getHostByIp(String ip)
	{
		
		for(Machine host: listHosts)
		{
			if(host.ip.equals(ip))
			{
				return host;
			}
		}
		return null;
		
	}
	
}
